using System.ComponentModel.DataAnnotations;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NotifyCenter.EntityFrameworkCore;
using NotifyCenter.MediatR.Etos;
using NotifyCenter.NotificationRecords;
using NotifyCenter.Projects;
using Yitter.IdGenerator;

namespace NotifyCenter.Sender;

[ApiController]
[Route("api/sender")]
public class SenderController(NotifyCenterDbContext dbContext, ILogger<SenderController> logger, IMediator publisher) : ControllerBase
{
    [HttpPost("enqueue")]
    public async Task<ActionResult<SenderEnqueueResult>> EnqueueAsync([FromBody] SenderEnqueueRequest input, CancellationToken cancellationToken)
    {
        var project = await dbContext.Set<Project>()
            .AsNoTracking()
            .FirstOrDefaultAsync(t => t.Id == input.ProjectId, cancellationToken);
        if (project is null)
        {
            return NotFound(new SenderEnqueueResult
            {
                Code = "ProjectNotFound",
                Message = $"Project {input.ProjectId} was not found."
            });
        }

        var notificationRule = await dbContext.Set<NotificationRule>()
            .FirstOrDefaultAsync(t => t.ProjectId == input.ProjectId && t.Id == input.NotificationId, cancellationToken);
        if (notificationRule is null)
        {
            return NotFound(new SenderEnqueueResult
            {
                Code = "NotificationNotFound",
                Message = $"Notification rule {input.NotificationId} was not found in project {input.ProjectId}."
            });
        }

        await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);

        try
        {
            var recordId = YitIdHelper.NextId();

            var record = new NotificationRecord
            {
                Id = recordId,
                BeginAt = DateTime.UtcNow,
                SentAt = null,
                ProjectId = project.Id,
                NotificationRuleId = notificationRule.Id,
                RetryTime = 0,
                Status = MessageSendingStatus.Pending,
                ConfigurationJson = notificationRule.ConfigurationJson,
                ErrorMsg = string.Empty
            };

            await dbContext.Set<NotificationRecord>().AddAsync(record, cancellationToken);
            await dbContext.SaveChangesAsync(cancellationToken);

            await publisher.Publish(new MessageEnqueueEto
            {
                RecordId = recordId,
                Type = notificationRule.Type,
                ConfigurationJson = notificationRule.ConfigurationJson,
                Receiver = input.Receiver,
                Content = input.Content,
                Attempt = 1
            }, cancellationToken);

            await transaction.CommitAsync(cancellationToken);

            logger.LogInformation("Message {RecordId} has been enqueued.", recordId);

            return Ok(new SenderEnqueueResult
            {
                Code = "Success",
                Message = "Enqueued."
            });
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);
            logger.LogError(ex, "Failed to enqueue message.");
            return Ok(new SenderEnqueueResult
            {
                Code = "Failed",
                Message = $"Failed to enqueue message. Error: {ex.Message}"
            });
        }
    }
}

public class SenderEnqueueResult
{
    public string Code { get; set; } = "Success";
    public string Message { get; set; } = "";
}

public class SenderEnqueueRequest
{
    [Range(1, long.MaxValue)]
    public long ProjectId { get; set; }

    [Range(1, long.MaxValue)]
    public long NotificationId { get; set; }

    [Required, MinLength(1), MaxLength(4096)]
    public string Receiver { get; set; } = string.Empty;

    [Required, MinLength(1)]
    public string Content { get; set; } = string.Empty;
}
